using DataAnalyticsTools.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DataAnalyticsTools.Core.ChangePointDetect
{
    /// <summary>
    /// 滑动窗口变化点检测器
    /// 通过比较变化点前后窗口的统计特性来检测均值变化
    /// </summary>
    public static class SlidingWindowDetector
    {
        /// <summary>
        /// 最大置信度上限
        /// </summary>
        private const float MAX_CONFIDENCE = 0.95f;

        /// <summary>
        /// 使用滑动窗口方法检测变化点
        /// </summary>
        /// <param name="values">待检测的时间序列数据</param>
        /// <param name="windowSize">滑动窗口大小</param>
        /// <param name="sensitivityFactor">灵敏度因子（默认1.5，建议范围0.8-3.0）
        /// - 值越小: 更敏感，检测更多变化点
        /// - 值越大: 更保守，只检测显著变化
        /// 公式: threshold = sensitivityFactor * (1.0 + 1.0 / windowSize)
        /// 注: 窗口越小，基础阈值越高，因为小窗口更容易受噪声影响</param>
        /// <returns>检测到的变化点列表</returns>
        public static List<ChangePoint> Detect(float[] values, int windowSize, float sensitivityFactor = 1.5f)
        {
            var changePoints = new List<ChangePoint>();

            // 需要至少 2*windowSize + 1 个点才能进行滑动窗口检测
            if (values.Length < windowSize * 2 + 1)
                return changePoints;

            for (int i = windowSize; i < values.Length - windowSize; i++)
            {
                // 左窗口：变化点之前的windowSize个点
                var leftWindow = values.Skip(i - windowSize).Take(windowSize).ToArray();
                // 右窗口：从变化点开始的windowSize个点
                var rightWindow = values.Skip(i).Take(windowSize).ToArray();

                float leftMean = leftWindow.Average();
                float rightMean = rightWindow.Average();
                float leftStd = MathHelper.CalculateStdDev(leftWindow);
                float rightStd = MathHelper.CalculateStdDev(rightWindow);

                // 计算均值变化的统计显著性（使用合并标准差）
                float meanDiff = Math.Abs(leftMean - rightMean);
                float pooledStd = (float)Math.Sqrt((leftStd * leftStd + rightStd * rightStd) / 2);

                // 标准化分数：均值差除以合并标准差（Cohen's d）
                float score = meanDiff / (pooledStd + MathHelper.MIN_STD_DEV);

                // 计算阈值，避免在平稳数据中检测到过多变化点
                // 窗口越小，阈值越高，因为小窗口更容易受噪声影响
                float threshold = sensitivityFactor * (1.0f + 1.0f / windowSize);

                if (score > threshold)
                {
                    // 判断变化方向
                    TrendDirection changeType = rightMean > leftMean
                        ? TrendDirection.Increasing
                        : TrendDirection.Decreasing;

                    // 计算置信度：使用双样本t检验的思想
                    // score本身就是标准化的均值差（相当于t统计量的形式）

                    // 1. 统计显著性：score相对于阈值的比率
                    float statisticalSignificance = score / threshold;

                    // 2. 效应大小 (Cohen's d)：score本身已经是标准化的均值差
                    // Cohen's d解释：0.2=小效应, 0.5=中效应, 0.8=大效应
                    float effectSize = score; // score = meanDiff / pooledStd，就是Cohen's d

                    // 3. 综合置信度：使用几何平均数
                    // 当效应大小小时（<0.5），即使统计显著性高，置信度也会被适当降低
                    float rawConfidence = (float)Math.Sqrt(statisticalSignificance * Math.Min(effectSize, 1.0f));

                    // 4. 映射到[0, MAX_CONFIDENCE]区间
                    float confidence = Math.Min(MAX_CONFIDENCE, 1.0f - (float)Math.Exp(-rawConfidence));

                    changePoints.Add(new ChangePoint(
                        index: i,
                        score: score,
                        changeType: changeType,
                        magnitude: meanDiff,
                        confidence: confidence
                    ));
                }
            }

            return changePoints;
        }
    }
}
